// CombatEngine.cs
//
// Copyright (c) 2007 Mike Strobel
//
// This source code is subject to the terms of the Microsoft Reciprocal License (Ms-RL).
// For details, see <http://www.opensource.org/licenses/ms-rl.html>.
//
// All other rights reserved.

using System;
using System.Collections.Generic;
using System.Linq;

using Supremacy.Entities;
using Supremacy.Game;
using Supremacy.Orbitals;
using Supremacy.Universe;

using Wintellect.PowerCollections;
using System.Linq.Expressions;
using Supremacy.Utility;

namespace Supremacy.Combat
{
    public delegate void SendCombatUpdateCallback(CombatEngine engine, CombatUpdate update);

    public delegate void NotifyCombatEndedCallback(CombatEngine engine);

    public abstract class CombatEngine
    {
        public readonly object SyncLock;

        private readonly int _combatId;
        private int _roundNumber;
        private bool _running;
        private bool _allSidesStandDown;
        private bool _ready;
        private readonly IList<CombatAssets> _assets;
        private readonly SendCombatUpdateCallback _updateCallback;
        private readonly NotifyCombatEndedCallback _combatEndedCallback;
        private readonly Dictionary<int, CombatOrders> _orders;

        protected int CombatID
        {
            get { return _combatId; }
        }

        protected int RoundNumber
        {
            get { return _roundNumber;  }
        }

        protected IList<CombatAssets> Assets
        {
            get { return _assets; }
        }

        protected Dictionary<int, CombatOrders> Orders
        {
            get { return _orders; }
        }

        protected bool Running
        {
            get
            {
                lock (SyncLock)
                {
                    return _running;
                }
            }
            private set
            {
                lock (SyncLock)
                {
                    _running = value;
                    if (_running)
                        _ready = false;
                }
            }
        }

        public bool IsCombatOver
        {
            get
            {
                if (_allSidesStandDown)
                    return true;

                int survivors = 0;
                foreach (CombatAssets assets in _assets)
                {
                    if (assets.HasSurvivingAssets)
                        survivors++;
                }
                return (survivors <= 1);
            }
        }

        public bool Ready
        {
            get
            {
                lock (SyncLock)
                {
                    if (Running || IsCombatOver)
                        return false;
                    return _ready;
                }
            }
        }

        protected CombatEngine(
            IList<CombatAssets> assets,
            SendCombatUpdateCallback updateCallback,
            NotifyCombatEndedCallback combatEndedCallback)
        {
            if (assets == null)
                throw new ArgumentNullException("assets");
            if (updateCallback == null)
                throw new ArgumentNullException("updateCallback");
            if (combatEndedCallback == null)
                throw new ArgumentNullException("combatEndedCallback");

            _running = false;
            _allSidesStandDown = false;
            _combatId = GameContext.Current.GenerateID();
            _roundNumber = 1;
            _assets = assets;
            _updateCallback = updateCallback;
            _combatEndedCallback = combatEndedCallback;
            _orders = new Dictionary<int, CombatOrders>();

            SyncLock = _orders;
        }

        public void SubmitOrders(CombatOrders orders)
        {
            lock (SyncLock)
            {
                if (!_orders.ContainsKey(orders.OwnerID))
                    _orders[orders.OwnerID] = orders;

                List<int> outstandingOrders = new List<int>();
                foreach (CombatAssets assets in Assets)
                {
                    outstandingOrders.Add(assets.OwnerID);
                }
                lock (_orders)
                {
                    foreach (int civId in _orders.Keys)
                        outstandingOrders.Remove(civId);
                    if (outstandingOrders.Count == 0)
                        _ready = true;
                }
            }
        }

        public void ResolveCombatRound()
        {
            bool isCombatOver;

            lock (_orders)
            {
                this.Running = true;

                foreach (CombatAssets asset in _assets)
                    asset.CombatID = _combatId;

                if ((RoundNumber > 1) || !CheckAllSidesStandDown())
                    ResolveCombatRoundCore();

                UpdateOrbitals();

                PerformRetreat();

                isCombatOver = IsCombatOver;

                if (!isCombatOver)
                {
                    _roundNumber++;
                }

                _orders.Clear();
            }

            SendUpdates();

            RemoveDefeatedPlayers();

            this.Running = false;

            if (isCombatOver)
            {
                AsyncHelper.Invoke(_combatEndedCallback, this);
            }
        }

        private bool CheckAllSidesStandDown()
        {
            bool result = true;
            foreach (CombatAssets civAssets in this.Assets)
            {
                foreach (CombatUnit unit in civAssets.CombatShips)
                {
                    CombatOrder order = GetOrder(unit.Source);
                    if (order == CombatOrder.Engage)
                    {
                        result = false;
                        break;
                    }
                }
                if (!result)
                    break;
                foreach (CombatUnit unit in civAssets.NonCombatShips)
                {
                    if (GetOrder(unit.Source) == CombatOrder.Engage)
                    {
                        result = false;
                        break;
                    }
                }
                if (!result)
                    break;
                if (civAssets.Station != null)
                {
                    if (GetOrder(civAssets.Station.Source) == CombatOrder.Engage)
                    {
                        result = false;
                        break;
                    }
                }
            }
            _allSidesStandDown = result;
            return result;
        }

        public void SendInitialUpdate()
        {
            SendUpdates();
        }

        private void SendUpdates()
        {
            foreach (CombatAssets playerAssets in _assets)
            {
                Civilization owner = playerAssets.Owner;
                CombatUpdate update;
                List<CombatAssets> friendlyAssets = new List<CombatAssets>();
                List<CombatAssets> hostileAssets = new List<CombatAssets>();
                friendlyAssets.Add(playerAssets);
                foreach (CombatAssets otherAssets in _assets)
                {
                    if (otherAssets == playerAssets)
                        continue;
                    if (CombatHelper.WillEngage(owner, otherAssets.Owner))
                        hostileAssets.Add(otherAssets);
                    else
                        friendlyAssets.Add(otherAssets);
                }
                update = new CombatUpdate(
                    _combatId,
                    _roundNumber,
                    _allSidesStandDown,
                    owner,
                    playerAssets.Location,
                    friendlyAssets,
                    hostileAssets);
                AsyncHelper.Invoke(_updateCallback, this, update);
            }
        }

        private void RemoveDefeatedPlayers()
        {
            for (int i = 0; i < _assets.Count; i++)
            {
                if (!_assets[i].HasSurvivingAssets)
                    _assets.RemoveAt(i--);
            }
        }

        private void UpdateOrbitals()
        {
            foreach (CombatAssets assets in Assets)
            {
                assets.UpdateAllSources();
            }
        }

        private void PerformRetreat()
        {
            foreach (CombatAssets assets in this.Assets)
            {
                for (int i = 0; i < assets.CombatShips.Count; i++)
                {
                    if (GetOrder(assets.CombatShips[i].Source) == CombatOrder.Retreat)
                    {
                        assets.EscapedShips.Add(assets.CombatShips[i]);
                        assets.CombatShips.RemoveAt(i--);
                    }
                }

                for (int i = 0; i < assets.NonCombatShips.Count; i++)
                {
                    if (GetOrder(assets.NonCombatShips[i].Source) == CombatOrder.Retreat)
                    {
                        assets.EscapedShips.Add(assets.NonCombatShips[i]);
                        assets.NonCombatShips.RemoveAt(i--);
                    }
                }

                if (assets.EscapedShips.Count == 0)
                    continue;

                var nearestFriendlySystem = GameContext.Current.Universe.FindNearestOwned<Colony>(
                        assets.Location,
                        assets.Owner);
                var destination = assets.Sector.GetNeighbors()
                    .OrderBy(
                        (o => GameContext.Current.Universe.FindAt<Orbital>(
                                  o.Location,
                                  (Expression<Func<Orbital, bool>>)
                                  (p => (p.OwnerID != assets.OwnerID) && p.IsCombatant))
                              .Cast<Orbital>()
                              .Sum(q => (q.OrbitalDesign.PrimaryWeapon.Damage * q.OrbitalDesign.PrimaryWeapon.Count)
                                        + (q.OrbitalDesign.SecondaryWeapon.Damage * q.OrbitalDesign.SecondaryWeapon.Count))))
                    .ThenBy(o => MapLocation.GetDistance(o.Location, nearestFriendlySystem.Location))
                    .FirstOrDefault();
                if (destination != null)
                {
                    foreach (CombatUnit shipStats in assets.EscapedShips)
                    {
                        ((Ship)shipStats.Source).Fleet.Location = destination.Location;
                    }
                }
            }
        }

        protected CombatAssets GetAssets(Civilization owner)
        {
            foreach (CombatAssets assets in this.Assets)
            {
                if (assets.Owner == owner)
                    return assets;
            }
            return null;
        }

        protected CombatOrder GetOrder(Orbital source)
        {
            try
            {
                return Orders[source.OwnerID].GetOrder(source);
            }
            catch
            {
                return CombatOrder.Retreat;
            }
        }

        protected abstract void ResolveCombatRoundCore();
    }
}
